﻿using System;
using System.Collections.Generic;
using System.Text;
using NGinnBPM.Lib.Data;
using NGinnBPM.Lib.Exceptions;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;

namespace NGinnBPM.Lib.Schema
{
    /// <summary>
    /// Process definition
    /// </summary>
    [Serializable]
    [DataContract]
    public class ProcessDefinition : IHaveExtensionProperties
    {
        private string _name;
        private int _version;
        private CompositeTask _body;
        private TypeSet _processTypes = new TypeSet();
        private string _displayName;
        private bool _bodyInited = false;

        public ProcessDefinition()
        {
            _body = new CompositeTask();
            _body.InitSchema(null, this);
        }

        [DataMember]
        public string Name
        {
            get { return _name; }
            set 
            { 
                _name = value;
                _body.Id = LocalName;
            }
        }

        [DataMember]
        public int Version
        {
            get { return _version; }
            set 
            { 
                _version = value;
                _body.Id = LocalName;
            }
        }

        [DataMember]
        public string PackageName { get; set; }

        [IgnoreDataMember]
        public string DefinitionId
        {
            get { return string.IsNullOrEmpty(PackageName) ? LocalName : string.Format("{0}.{1}.{2}", PackageName, Name, Version); }
        }

        [IgnoreDataMember]
        public string LocalName
        {
            get { return string.Format("{0}.{1}", Name, Version); }
        }

        [DataMember(IsRequired = false, EmitDefaultValue = false)]
        public string DisplayName
        {
            get { return _displayName == null ? Name : _displayName; }
            set { _displayName = value; }
        }

        private PackageDefinition _pkg;

        [IgnoreDataMember]
        public PackageDefinition Package
        {
            get { return _pkg; }
            set
            {
                _pkg = value;
                PackageName = _pkg == null ? null : _pkg.Name;
            }
        }
        
        [DataMember]
        public CompositeTask Body
        {
            get 
            {
                if (!_bodyInited)
                {
                    _body.InitSchema(null, this);
                    _bodyInited = true;
                }
                return _body; 
            }
            set 
            { 
                _body = value;
                if (_body != null)
                {
                    _body.InitSchema(null, this);
                    _bodyInited = true;
                    _body.Id = string.Format("{0}.{1}", this.Name, this.Version);
                }
            }
        }

        public bool Validate(IList<ValidationMessage> msgs)
        {
            if (Body == null)
            {
                throw new Exception("Process body not defined");
            }
            return Body.Validate(msgs);
        }

        public NetNode GetNode(string id)
        {
            if (id == Body.Id) return Body;
            return Body.GetNodeRecursive(id);
        }

        public IList<NetNode> GetAllNodesRecursive()
        {
            List<NetNode> l = new List<NetNode>();
            l.Add(Body);
            l.AddRange(Body.GetNodesRecursive());
            return l;
        }

        public IList<Task> GetAllTasksRecursive()
        {
            List<Task> l = new List<Task>();
            l.Add(Body);
            foreach (NetNode nn in Body.GetNodesRecursive())
                if (nn is Task) l.Add((Task)nn);
            return l;
        }

        public Task GetTask(string id)
        {
            NetNode nn = GetNode(id);
            if (nn == null) return null;
            if (!(nn is Task)) throw new Exception("Node is not a task");
            return (Task)nn;
        }

        public Place GetPlace(string id)
        {
            NetNode nn = GetNode(id);
            if (nn == null) return null;
            if (!(nn is Place)) throw new Exception("Node is not a place");
            return (Place)nn;
        }

        public Task RequireTask(string id)
        {
            Task tsk = GetTask(id);
            if (tsk == null) throw new UndefinedNetNodeException(DefinitionId, id);
            return tsk;
        }

        public Place RequirePlace(string id)
        {
            Place pl = GetPlace(id);
            if (pl == null) throw new UndefinedNetNodeException(DefinitionId, id);
            return pl;
        }

        [DataMember]
        public TypeSet DataTypes
        {
            get { return _processTypes; }
            set { _processTypes = value; }
        }

        /// <summary>
        /// Return process input data definition
        /// </summary>
        /// <returns></returns>
        public StructDef GetInputDataSchema()
        {
            return Body.GetInputDataSchema();
        }

        /// <summary>
        /// Return process output data definition
        /// </summary>
        /// <returns></returns>
        public StructDef GetOutputDataSchema()
        {
            return Body.GetOutputDataSchema();
        }

        /// <summary>
        /// Return process internal data definition
        /// </summary>
        /// <returns></returns>
        public StructDef GetInternalDataSchema()
        {
            return Body.GetInternalDataSchema();
        }

        public string GetInputDataXmlSchema()
        {
            StructDef sd = Body.GetInputDataSchema();
            return DataTypes.GetDataSchema(sd, Name, null);
        }

        public string GetOutputDataXmlSchema()
        {
            StructDef sd = Body.GetOutputDataSchema();
            return DataTypes.GetDataSchema(sd, Name, null);

        }

        public string GetInternalDataXmlSchema()
        {
            StructDef sd = Body.GetInternalDataSchema();
            return DataTypes.GetDataSchema(sd, Name, null);
        }

        public string GetAllProcessAndTaskDataSchemas()
        {
            TypeSet allTypes = new TypeSet();
            allTypes.ImportTypes(this.DataTypes);
            this.Body.GetAllTaskDataSchemas(allTypes);

            StringBuilder sb = new StringBuilder();
            XmlWriterSettings ws = new XmlWriterSettings();
            ws.OmitXmlDeclaration = true; ws.Indent = true;
            XmlWriter xw = XmlWriter.Create(sb, ws);
            xw.WriteStartDocument();
            xw.WriteStartElement("xs", "schema", XmlSchemaUtil.SCHEMA_NS);
            allTypes.WriteXmlSchema(xw);
            xw.WriteEndDocument();
            xw.Flush();
            return sb.ToString();
        }

        [DataMember(IsRequired = false, EmitDefaultValue = false)]
        public Dictionary<string, string> AdditionalScriptImports { get; set; }
        

        #region IHaveExtensionProperties Members

        [DataMember(IsRequired = false, EmitDefaultValue = false)]
        public Dictionary<string, string> ExtensionProperties { get; set; }

        public IEnumerable<string> GetExtensionProperties(string xmlns)
        {
            return ExtensionPropertyHelper.GetExtensionProperties(ExtensionProperties, xmlns);
        }

        public string GetExtensionProperty(string xmlns, string name)
        {
            return ExtensionPropertyHelper.GetExtensionProperty(ExtensionProperties, xmlns, name);
        }

        public void SetExtensionProperty(string xmlns, string name, string value)
        {
            if (ExtensionProperties == null) ExtensionProperties = new Dictionary<string, string>();
            ExtensionPropertyHelper.SetExtensionProperty(ExtensionProperties, xmlns, name, value);
        }

        public string GetExtensionProperty(string fullName)
        {
            return ExtensionPropertyHelper.GetExtensionProperty(ExtensionProperties, fullName);
        }

        #endregion

        [OnDeserialized]
        private void SetValuesOnDeserialized(StreamingContext context)
        {
            this.Body.InitSchema(null, this);
        }

        public void SerializeToXml(XmlWriter output)
        {
            DataContractSerializer ser = new DataContractSerializer(typeof(ProcessDefinition), new Type[] { typeof(CompositeTask), typeof(AtomicTask), typeof(TypeSet), typeof(TypeDef), typeof(EnumDef), typeof(StructDef) }, 10000, false, false, null);
            ser.WriteObject(output, this);
        }

        public string SerializeToXmlString()
        {
            StringBuilder sb = new StringBuilder();

            XmlWriter xw = XmlWriter.Create(sb, new XmlWriterSettings
            {
                Indent = true
            });
            SerializeToXml(xw);
            xw.Flush();
            return sb.ToString();
        }
    }
}
